package socket

import (
	"encoding/binary"
	"fmt"
	"io"
	"net"

	"go.uber.org/zap"
)

type ConnectHandler interface {
	ConnectActive(connCtx *ConnectContext) (err error)
	ConnectInactive(connCtx *ConnectContext) (err error)
	ConnectRead(connCtx *ConnectContext, in []byte) (err error)
	ErrorCatch(connCtx *ConnectContext, cause error) (err error)
}

func NewContext(conn net.Conn, endian binary.ByteOrder, connectHandler ConnectHandler) *ConnectContext {
	var connectContext ConnectContext
	connectContext.id = fmt.Sprintf("%p", &connectContext)
	connectContext.conn = conn
	connectContext.endian = endian
	connectContext.connectHandler = connectHandler
	connectContext.attrs = make(map[string]interface{})
	return &connectContext
}

type ConnectContext struct {
	id             string
	conn           net.Conn         //网络的真实连接
	endian         binary.ByteOrder //默认大端序
	connectHandler ConnectHandler
	active         bool                   //连接是否还活跃
	attrs          map[string]interface{} //存放自定义的属性
}

func (ctx *ConnectContext) process() {
	err := ctx.connectHandler.ConnectActive(ctx)
	if err != nil {
		err = ctx.connectHandler.ErrorCatch(ctx, err)
		if err == io.EOF {
			ctx.onClose()
			return
		}
	}
	ctx.active = true
	for ctx.active {
		data, err := ctx.connectRead()
		if !ctx.checkError(err) {
			err = ctx.connectHandler.ConnectRead(ctx, data)
			ctx.checkError(err)
		}
	}
	ctx.onClose()
}

func (ctx *ConnectContext) onClose() {
	err := ctx.conn.Close()
	if err != nil {
		fmt.Println("onClose", err)
	}
	err = ctx.connectHandler.ConnectInactive(ctx)
	if err != nil {
		ctx.connectHandler.ErrorCatch(ctx, err)
	}
	zap.L().Info("连接已关闭")
}

func (ctx *ConnectContext) checkError(err error) bool {
	if err == nil {
		return false
	}
	if err == io.EOF {
		ctx.connectHandler.ErrorCatch(ctx, err)
		ctx.active = false
		return true
	}
	_, isOpError := err.(*net.OpError)
	if isOpError {
		ctx.connectHandler.ErrorCatch(ctx, err)
		ctx.active = false
		return true
	}
	err = ctx.connectHandler.ErrorCatch(ctx, err)
	if err == nil {
		return false
	} else {
		//TODO 日志
		ctx.active = false
		return true
	}
}

func (ctx *ConnectContext) connectRead() ([]byte, error) {
	sizeBuffer := make([]byte, 4)
	_, err := ctx.conn.Read(sizeBuffer)
	if err != nil {
		return nil, err
	}
	var size uint32
	if ctx.endian == binary.LittleEndian {
		size = binary.LittleEndian.Uint32(sizeBuffer)
	} else {
		size = binary.BigEndian.Uint32(sizeBuffer)
	}
	byteBuffer := make([]byte, size)
	n, err := ctx.conn.Read(byteBuffer)
	if err != nil {
		return nil, err
	}
	if n != int(size) {
		return nil, err
	}
	return byteBuffer, nil
}

func (ctx *ConnectContext) ConnectWrite(message []byte) {
	buffer := make([]byte, 4, 4+len(message))
	if ctx.endian == binary.LittleEndian {
		binary.LittleEndian.PutUint32(buffer, uint32(len(message)))
	} else {
		binary.BigEndian.PutUint32(buffer, uint32(len(message)))
	}
	buffer = append(buffer, message...)
	_, err := ctx.conn.Write(buffer)
	ctx.checkError(err)
}

func (ctx *ConnectContext) Close() {
	ctx.active = false
	ctx.onClose()
}

func (ctx *ConnectContext) PutAttr(name string, value interface{}) {
	ctx.attrs[name] = value
}

func (ctx *ConnectContext) GetAttr(name string) interface{} {
	return ctx.attrs[name]
}
